#include "application_test.h"
#include "unistd.h"
#include "list.h"
//#include "simclist.h"
static Lista* messages = NULL;
static Lista* toSend = NULL;
//list_t messages;
//list_t toSend;


void cleanRequestedBottles() {	
	for(i = 0; i < neighca; i++)
		for(j = 0; j < neighca + 1 ; j++)
			requested_bottle[i][j] = 0;
}
void printTurns() {
	printf("%% %d %% Turns debug - {", ranka);
	for(i = 0; i < neighca; i++)
			printf(" %d,", holds_turn[i]);
	printf("} \n");

	printf("%% %d %% Turns - {", ranka);
	for(i = 0; i < neighca; i++) {
		if(holds_turn[i] == 1)
			printf(" %d,", neighva[i]);
	}
	printf("} \n");
}
void printHoldBottle() {
	printf("%% %d %% Holds - ", ranka);
	for(i = 0; i < neighca; i++) {
		printf("(%d){ ", neighva[i]);
		for(j = 0; j < neighca + 1 ; j++) {
			if(holds_bottle[i][j] == 1)
				if(j != neighca)
					printf(" %d,", neighva[j]);
				else
					printf(" %d,", ranka);
		}
		printf(" } \t");
	}
	printf("\n");
}

void printOwesBottle() {
	printf("%% %d %% Owes - ", ranka);
	for(i = 0; i < neighca; i++) {
		printf("(%d){ ", neighva[i]);
		for(j = 0; j < neighca + 1; j++) {
			if(owes_bottle[i][j] == 1)
				if(j != neighca)
					printf(" %d,", neighva[j]);
				else
					printf(" %d,", ranka);
		}
		printf(" } \t");
	}
	printf("\n");
}
// Sleep for a random time x within range [min, max]
// returns the seconds that it sleeps
static int randomSleep(int min, int max) {
	int n = rand() % max + min;
	sleep(1);
	return n;
}

int PD_Event(void *msg, int tag_type, int sender) {
	if (msg != NULL) {
		struct msgstruct* a = malloc(sizeof(struct msgstruct));
		*a = *((struct msgstruct*) msg);
		struct msgInfo* msg_info = malloc(sizeof(struct msgInfo));
		msg_info->msg = a;
		msg_info->sender = sender;

		printf("%% %d %% - PD_Event - Recebi mensagem,", ranka);
		if(a->bottle != -1)
			printf("Bottle = {%d}", a->bottle);
		if(a->request_bottle != -1)
			printf(", Request = {%d}", a->request_bottle);
		if(a->turn != -1)
			printf(" e Turn");
		printf(" do vizinho %d\n", sender);
		inserirFim(messages, msg_info);
//		list_append(&messages, msg_info);

	}
}
int PD_Pulse(int l, int *msgc, int **destv, void **msgv, int **type_msgv) {
	*msgc = 0;
	printf("%% %d %% - Estou no pulso: %d e desejo %d\n", ranka, l, neighva[needs_bottle]);
	printHoldBottle();
	processMsgs();

	printOwesBottle();
	printTurns();

	if(drinking == 0) {
		if(!requested) {
			requestBottles();	
			requested = 1;
		} else {
			int count = 0;
			for(i = 0; i < neighca; ++i) {
				if(holds_bottle[i][needs_bottle] == 1) {
					count++;
				}
				if(holds_bottle[i][neighca] == 1) {
					count++;
				}
			}
			if(count == 2*neighca) {
				drinking = 1;
		//		printf("%% %d %% - Posso beber!\n", ranka);
				//BEBER
			}
		}
	} else if(drinking == 1) {
		drinking = 0;
		printf("----------------------------------\n");
		printf("%% %d %% - Enchi a cara!\n", ranka);
		printf("----------------------------------\n");
		cleanRequestedBottles();
		giveAllBottles();
		requested = 0;
	}
	randomSleep(2,5);
	if(drinking == 0)
		sendMsgs(msgc, destv, msgv, type_msgv);
	return 1;
}
void requestBottles() {
	for(i = 0; i < neighca; i++) {
//		if(!requested_bottle[i][needs_bottle])
			if(!holds_bottle[i][needs_bottle]) {
				addMsg(neighva[needs_bottle], -1, -1 , i);
//				requested_bottle[i][needs_bottle] = 1;
			}
//		if(!requested_bottle[i][neighca])
			if(!holds_bottle[i][neighca]) { 
				addMsg(ranka, -1, -1, i);
//				requested_bottle[i][neighca] = 1;
			}
	}
}
void updateNeeds() {
	old_needs_bottle = needs_bottle;
	needs_bottle = rand() % neighca;
}
void giveAllBottles() {
	printTurns();
	int turnsents = 0;
	int turntoSent = 0;
	for(i = 0; i < neighca; i++) {
		turntoSent++;
//		if(holds_turn[i] == 1) {
//			addMsg(-1, 1, -1, i); 
//		}
	}
	int i;
	for(i = 0; i < neighca; i++) {
		if(holds_turn[i] == 1) {
			holds_turn[i] = 0;
			addMsg(-1, 1, -1, i); 
			turnsents++;
			int cont = 0;
			int turn_sent = 1;
			for(j = 0; j < neighca + 1; j++) {
				if(owes_bottle[i][j] == 1) {
					owes_bottle[i][j] = 0;
					holds_bottle[i][j] = 0;
					if(j != neighca) {
						if(turn_sent == 0) {
							turn_sent = 1;
							addMsg(-1, 1, neighva[j], i);
						} else {
							addMsg(-1, -1, neighva[j], i);
						}
					}
					else {
						if(turn_sent == 0) {
							turn_sent = 1;
							addMsg(-1, 1, ranka, i); 
						} else {
							addMsg(-1, -1, ranka, i);
						}
					}

					cont++;
				}
				//else {
				//	addMsg(-1, 1, -1, i); 
				//}
			}
			//if(cont == 0)
		}
	}
	if(turnsents != turntoSent) {
				printf("%% %d %% - Linha %d - Turn nao foi enviado.\n", ranka, __LINE__);
		exit(1);
	}
	updateNeeds();
}

void addMsg(int request_bottle, int turn, int bottle, int who) {
	struct msgstruct* msg = calloc(1, sizeof(struct msgstruct));
	msg->request_bottle = request_bottle;
	msg->turn = turn;
	msg->bottle = bottle;
	struct msgInfo* msginfo = calloc(1, sizeof(struct msgInfo));
	msginfo->msg = msg;
	msginfo->sender = who;
	inserirFim(toSend, msginfo);
//	list_append(&toSend, msginfo);
	printf("\t%% %d %% - ADDMSG: Para %d, bottle (%d), request (%d) e turn (%d) \n",ranka, neighva[who], msginfo->msg->bottle, msginfo->msg->request_bottle, msginfo->msg->turn);
	//GAMBIARRA
	if(bottle != -1) {
		holds_bottle[who][findNeighbor(bottle)] = 0;
//		if(owes_bottle[who][findNeighbor(bottle)])
//			owes_bottle[who][findNeighbor(bottle)] = 0;
	}
}

void sendMsgs(int *msgc, int **destv, void **msgv, int **type_msgv) {
	*msgc = toSend->size;
//	*msgc = list_size(&toSend);
	int *destvaux;
	destvaux = (int*) calloc(*msgc, sizeof(int));
	int *typevaux;
	typevaux = (int*)calloc(*msgc, sizeof(int));
	struct msgstruct *msgvaux;
	msgvaux = (struct msgstruct *)calloc(*msgc, sizeof(struct msgstruct));

	printf("%% %d %% - Enviando %d mensagens {\n", ranka, *msgc);
	for(i = 0; i < *msgc; ++i)  {
		struct msgInfo* info = primeiro(toSend);
//		struct msgInfo* info = list_get_at(&toSend, 0);
		destvaux[i] = neighva[info->sender];
		typevaux[i] = type_index;
		msgvaux[i] = *(info->msg);
		printf("\t%% %d %% - Para %d, bottle (%d), request (%d) e turn (%d) \n",ranka, destvaux[i], info->msg->bottle, info->msg->request_bottle, info->msg->turn);
		free(info->msg);
		free(info);
		info = 0;
		removerInicio(toSend);
		
	}
	printf("}\n");
	*destv = destvaux;
	*type_msgv = typevaux;
	*msgv = msgvaux;;
}

void processMsgs() {
	int process = 0;
	if(drinking == 1)
		return;

/*	No* aux = primeiroNo(messages);
	while(!ultimoNo(messages, aux) {
		aux = proximoNo(aux);
	}*/

	while(!taVazia(messages)) {
//	while(list_size(&messages) != 0) {
		struct msgstruct message;
		process++;
		message = *(((struct msgInfo*)primeiro(messages))->msg);
//		struct msgInfo* inf = ((struct msgInfo*)list_get_at(& messages, 0));
//		message = *(inf->msg);
		No* no = primeiroNo(messages);
		int sender = ((struct msgInfo*)messages->inicio->prox->valor)->sender;
//		int sender = inf->sender;
		int nBottle = message.bottle;	
		int nsender = findNeighbor(sender);
		int nrequest_bottle = message.request_bottle;
		int nturn = message.turn;

/*		printf("%% %d %% - Recebi mensagem,", ranka);
		if(nBottle != -1)
			printf("Bottle = {%d}", message.bottle);
		if(nrequest_bottle != -1)
			printf(", Request = {%d}", message.request_bottle);
		if(nturn != -1)
			printf(" e Turn");
		printf(" do vizinho %d\n", sender);*/


		if(nBottle != -1) {
			if(holds_bottle[nsender][findNeighbor(nBottle)] == 1) {
				printf("%% %d %% - Linha %d - Garrafa recebida duas vezes.\n", ranka, __LINE__);
			}
			if(!(nBottle == neighva[needs_bottle]) && !(nBottle == ranka)) {
				printf("%% %d %% - Linha %d - Garrafa recebida, mas nao necessaria.\n", ranka, __LINE__);
				addMsg(-1,-1,nBottle, nsender);
			} else {
				holds_bottle[nsender][findNeighbor(nBottle)] = 1;
				if(nrequest_bottle != -1 && nrequest_bottle == nBottle) {
//					printf("owes_bottle[nsender][findNeighbor(nrequest_bottle)] = 1;\n");
					owes_bottle[nsender][findNeighbor(nrequest_bottle)] = 1; // for all bk \in Y
				} else if(nrequest_bottle != -1) {
					if(holds_bottle[nsender][findNeighbor(nrequest_bottle)] == 1) {
						holds_bottle[nsender][findNeighbor(nrequest_bottle)] = 0;
						addMsg(-1, -1, nrequest_bottle, nsender);
					}
				}
			}
			if(nturn != -1) {
				holds_turn[nsender] = 1;
			}
		}
		else if(nrequest_bottle != -1) { // Request

			printf("%% %d %% - Processando request = %d \n", ranka, nrequest_bottle);
			if(holds_bottle[nsender][findNeighbor(nrequest_bottle)] == 0) {
					printf("%% %d %% - Linha %d - Pediram uma garrafa que nao possuo.\n", ranka, __LINE__);
					while(!ultimoNo(messages, no)) {

						struct msgInfo* info = (struct msgInfo*) no->valor;
						if(info->msg->bottle != -1 && info->msg->bottle == nrequest_bottle) {
							printf("%% %d %% - Linha %d - Allberson safado.\n", ranka, __LINE__);
							exit(239084);
						}
						no = proximoNo(no);
					}
			}
			else {
				if(!holds_turn[nsender] || ((nrequest_bottle != neighva[needs_bottle]) && nrequest_bottle != ranka)) {
	//				if(nrequest_bottle == neighva[needs_bottle])
					holds_bottle[nsender][findNeighbor(nrequest_bottle)] = 0;
		//			else if(nrequest_bottle == ranka)
			//			holds_bottle[nsender][ranka] = 0;
					int torequest = -1;
					if(neighva[needs_bottle] == nrequest_bottle) {
						torequest = neighva[needs_bottle];
					}
					if(ranka == nrequest_bottle) {
						torequest = ranka;
					}
					addMsg(torequest, -1, nrequest_bottle, nsender);
				}
				else {
	//				printf("%% %d %% - Owes modificado no no %d garrafa %d\n", ranka, sender, nrequest_bottle);
					owes_bottle[nsender][findNeighbor(nrequest_bottle)] = 1;
				}
			}
		}
		else if(nturn != -1) {
			if(holds_turn[nsender] == 1)
				printf("%% %d %% - Linha %d - Enviaram um turno que ja eh meu.\n", ranka, __LINE__);
			holds_turn[nsender] = 1;
		}
		free(((struct msgInfo*)primeiro(messages))->msg);
		free(((struct msgInfo*)primeiro(messages)));
		removerInicio(messages);
	}
	if(process != 0)
		printf("%% %d %% - %d mensagens processadas.\n", ranka, process);
}
int findNeighbor(int sender) {
	if(sender == ranka)
		return neighca;
	for(i = 0; i < neighca; i++) {
		if(sender == neighva[i]) {
			return i;
		}
	}
}

int PD_Init(int argc, char *argv[], int rank, int neighc, int *neighv) {
	neighca = neighc;
	neighva = neighv;
	type_index =
	    PD_Create_derived_datatype(3, blocklengths, types,
				       sizeof(struct msgstruct));
	ranka = rank;
	
	messages = criarLista();
	toSend = criarLista();
//	list_init(&messages);	
//  list_init(&toSend);	

	holds_bottle = (int**) calloc(neighca, sizeof(int*));
	for(i = 0; i < neighca; i++)
		holds_bottle[i] =  (int*) calloc(neighca + 1, sizeof(int));

	requested_bottle = (int**) calloc(neighca, sizeof(int*));
	for(i = 0; i < neighca; i++)
		requested_bottle[i] =  (int*) calloc(neighca + 1, sizeof(int));

	holds_turn = (int*) calloc(neighca, sizeof(int));

	owes_bottle = (int**) calloc(neighca, sizeof(int*));
	for(i = 0; i < neighca; i++)
		owes_bottle[i] =  (int*) calloc(neighca + 1, sizeof(int));

	switch(rank) {
		case 0:
			for(i=0; i < neighca; i++) 
				for(j = 0; j < neighca + 1; j++)  {
					holds_bottle[i][j] = 1;
				}
				holds_turn[findNeighbor(1)] = 1;
				holds_turn[findNeighbor(2)] = 1;
				old_needs_bottle = 1;
				needs_bottle = 1;
		break;
		case 1:
			for(i=0; i < neighca; i++) {
				holds_bottle[findNeighbor(2)][0] = 1;	
				holds_bottle[findNeighbor(2)][1] = 1;	
				holds_bottle[findNeighbor(2)][2] = 1;	
			}
			holds_turn[findNeighbor(2)] = 1;
			old_needs_bottle = 0;
			needs_bottle = 0;
		break;

		case 2:
		needs_bottle = 0;
		old_needs_bottle = 0;
/*			for(i=0; i < neighca; i++) {
				if(neighva[i] == 1)
					holds_bottle[0][i] = 1;
			}*/
		break;

	}
	printHoldBottle();
	srand(time(NULL));

}

int PD_Msg_buff_allocate(int size, int neigh, void **buff) {
	*buff = malloc(size);
//	*buff = calloc(1, size);
}

int PD_Finalize() {
//	liberarLista(messages);
//	liberarLista(toSend);
}
